#include "gui/Scroller.h"

#include "utilities/Rectangle.h"

// http://www.cplusplus.com/reference/algorithm/max/
#define max(a,b) ((b<a)?a:b)

// http://www.cplusplus.com/reference/algorithm/min/
#define min(a,b) ((a<b)?a:b)

namespace gui
{
	Scroller::Scroller() : imageUpSet( NULL ), imageDownSet( NULL ), imageScrollField( NULL ), imageScrollBar( NULL ), imageBackground( NULL ), font( NULL )
	{
	}

	Scroller::Scroller( const gdn::Vector2f& thePos, gdn::Vector2f theSize, const std::vector< std::string >& theList, size_t theDefault,
					    gdn::Image& theImageUp, gdn::Image& theImageDown, gdn::Image& theImageScrollField, gdn::Image& theImageScrollBar, gdn::Image& theImageBackground, gdn::Image& theImageHighlight,
				        gdn::Font& theFont, unsigned int theCharSize,

					    gdn::Rectanglei theRectUpIdle, gdn::Rectanglei theRectUpHover, gdn::Rectanglei theRectUpPress,

					    gdn::Rectanglei theRectDownIdle, gdn::Rectanglei theRectDownHover, gdn::Rectanglei theRectDownPress,

					    gdn::Rectanglei theRectBarTopIdle, gdn::Rectanglei theRectBarTopHover, gdn::Rectanglei theRectBarTopPress,
					    gdn::Rectanglei theRectBarIdle, gdn::Rectanglei theRectBarHover, gdn::Rectanglei theRectBarPress,
					    gdn::Rectanglei theRectBarBottomIdle, gdn::Rectanglei theRectBarBottomHover, gdn::Rectanglei theRectBarBottomPress,

					    gdn::Rectanglei theRectBackTopLeft, gdn::Rectanglei theRectBackTop, gdn::Rectanglei theRectBackTopRight,
					    gdn::Rectanglei theRectBackLeft, gdn::Rectanglei theRectBack, gdn::Rectanglei theRectBackRight,
					    gdn::Rectanglei theRectBackBottomLeft, gdn::Rectanglei theRectBackBottom, gdn::Rectanglei theRectBackBottomRight )
	   : pos( thePos ), size( theSize ), list( theList ), defaultItem( theDefault ), current( defaultItem ), top( 0 ), bottom( 0 ),
	     imageUpSet( &theImageUp ), imageDownSet( &theImageDown ), imageScrollField( &theImageScrollField ), imageScrollBar( &theImageScrollBar ), imageBackground( &theImageBackground ), imageHighlight( &theImageHighlight ),
	     font( &theFont ), charSize( theCharSize ),

	     rectUpIdle( theRectUpIdle ), rectUpHover( theRectUpHover ), rectUpPress( theRectUpPress ),

	     rectDownIdle( theRectDownIdle ), rectDownHover( theRectDownHover ), rectDownPress( theRectDownPress ),

	     rectBarTopIdle( theRectBarTopIdle ), rectBarTopHover( theRectBarTopHover ), rectBarTopPress( theRectBarTopPress ),
	     rectBarIdle( theRectBarIdle ), rectBarHover( theRectBarHover ), rectBarPress( theRectBarPress ),
	     rectBarBottomIdle( theRectBarBottomIdle ), rectBarBottomHover( theRectBarBottomHover ), rectBarBottomPress( theRectBarBottomPress ),

	     rectBackTopLeft( theRectBackTopLeft ), rectBackTop( theRectBackTop ), rectBackTopRight( theRectBackTopRight ),
	     rectBackLeft( theRectBackLeft ), rectBack( theRectBack ), rectBackRight( theRectBackRight ),
	     rectBackBottomLeft( theRectBackBottomLeft ), rectBackBottom( theRectBackBottom ), rectBackBottomRight( theRectBackBottomRight ),

	     bar( pos.x + size.x - rectUpIdle.width, pos.y + rectUpIdle.height, rectBarTopIdle.width, 25 ), barTopRect( &rectBarTopIdle ), barRect( &rectBarIdle ), barBottomRect( &rectBarBottomIdle )
	{
		upButton = gui::Button( gdn::Vector2f( pos.x + size.x - rectUpIdle.width, pos.y ),
			                    "", "", "",
					            gdn::Color( 0, 0, 0 ), gdn::Color( 0, 0, 0 ), gdn::Color( 0, 0, 0 ),
					            *imageUpSet, *font, 0,
					            rectUpIdle, rectUpHover, rectUpPress );

		downButton = gui::Button( gdn::Vector2f( pos.x + size.x - rectDownIdle.width, pos.y + size.y - rectDownIdle.height ),
			                      "", "", "",
					              gdn::Color( 0, 0, 0 ), gdn::Color( 0, 0, 0 ), gdn::Color( 0, 0, 0 ),
					              *imageDownSet, *font, 0,
					              rectDownIdle, rectDownHover, rectDownPress );

		//for ( size_t i = 0; i < list.size(); std::cout << list[ i++ ] << std::endl );

		hold = false;

		if ( list.size() < 8 )
		{
			bottom = list.size();
		}
		else
		{
			bottom = 8;
		}

		/*
		gdn::RenderImage boxTmp;
		boxTmp.Create( gdn::Vector2i( size.x - rectUpIdle.width, size.y ) );
		boxTmp.Clear();
		DrawBackground( boxTmp );
		boxCache = boxTmp.GetImage();
		//*/
	}

	int Scroller::Process( const gdn::Event& event )
	{
		if ( upButton.Process( event ) == GUI_BUTTON_RELEASE )
		{
			bar.y -= ( size.y - ( rectUpIdle.height + rectDownIdle.height ) ) / list.size();
		}
		if ( downButton.Process( event ) == GUI_BUTTON_RELEASE )
		{
			bar.y += ( size.y - ( rectUpIdle.height + rectDownIdle.height ) ) / list.size();
		}

		ProcessBarHighlight( event );
		ResizeBar();
		MoveBar();

		CalculateDisplayText();

		ProcessTextSelection( event );

		return 0;
	}

	void Scroller::Draw( gdn::Window& window )
	{
		upButton.Draw( window );
		downButton.Draw( window );
	}

	void Scroller::Draw( gdn::RenderImage& renderImage )
	{
		gdn::Sprite scrollField;
		scrollField.SetImage( *imageScrollField );

		for ( float i = pos.y + rectUpIdle.height; i < pos.y + size.y - rectDownIdle.height; i += imageScrollField->GetSize().y )
		{
			scrollField.SetPosition( pos.x + size.x - imageScrollField->GetSize().x, i );
			renderImage.Draw( scrollField );
			//std::cout << barTopRect->x << "---" << barTopRect->y << " " << barTopRect->width << "---" << barTopRect->height;
			//std::cout << " vs ";
			//std::cout << rectBarTopIdle.x << "---" << rectBarTopIdle.y << " " << rectBarTopIdle.width << "---" << rectBarTopIdle.height << std::endl;
		}

		DrawBar( renderImage );

		upButton.Draw( renderImage );
		downButton.Draw( renderImage );

		/*
		gdn::Sprite box;
		box.SetImage( boxCache );
		box.SetPosition( pos.x, pos.y );
		renderImage.Draw( box );
		//*/
		DrawBackground( renderImage );

		gdn::Sprite highlight;
		highlight.SetImage( *imageHighlight );

		gdn::Text text;
		text.SetFont( *font );
		text.SetCharacterSize( charSize );
		text.SetColor( gdn::Color( 0, 0, 0 ) );
		gdn::Vector2f currentTextPos( pos.x + 5, pos.y + 5 - 16 );
		for ( size_t i = top; i < bottom; ++i )
		{
			currentTextPos.y += 16;
			text.SetString( list[ i ] );
			text.SetPosition( currentTextPos );

			if ( i == current )
			{
				highlight.SetPosition( currentTextPos );
				renderImage.Draw( highlight );
			}

			renderImage.Draw( text );
		}
	}


	std::string Scroller::GetSelectedItem() const
	{
		return list[ current ];
	}


	void Scroller::ProcessBarHighlight( const gdn::Event& event )
	{
		if ( event.type == gdn::Event::MouseButtonPressed )
		{
			if ( event.mouseButton == gdn::Mouse::Left )
			{
				if ( util::RectangleContainsPoint< float >( bar, mousePos ) )
				{
					hold = true;
					barTopRect = &rectBarTopPress;
					barRect = &rectBarPress;
					barBottomRect = &rectBarBottomPress;
				}
			}
		}
		else if ( event.type == gdn::Event::MouseButtonReleased )
		{
			if ( event.mouseButton == gdn::Mouse::Left )
			{
				hold = false;

				if ( util::RectangleContainsPoint< float >( bar, mousePos ) )
				{
					barTopRect = &rectBarTopHover;
					barRect = &rectBarHover;
					barBottomRect = &rectBarBottomHover;
				}
				else
				{
					barTopRect = &rectBarTopIdle;
					barRect = &rectBarIdle;
					barBottomRect = &rectBarBottomIdle;
				}
			}
		}
		else if ( event.type == gdn::Event::MouseMoved )
		{
			mousePos.x = event.x;
			mousePos.y = event.y;

			if ( util::RectangleContainsPoint< float >( bar, mousePos ) )
			{
				if ( !hold )
				{
					barTopRect = &rectBarTopHover;
					barRect = &rectBarHover;
					barBottomRect = &rectBarBottomHover;
				}
				else
				{
					barTopRect = &rectBarTopPress;
					barRect = &rectBarPress;
					barBottomRect = &rectBarBottomPress;
				}
			}
			else if ( !util::RectangleContainsPoint< float >( bar, mousePos ) )
			{
				barTopRect = &rectBarTopIdle;
				barRect = &rectBarIdle;
				barBottomRect = &rectBarBottomIdle;
			}
		}
	}

	void Scroller::DrawBackground( gdn::RenderImage& renderImage )
	{
		/*
		gdn::Vector2f posTmp = pos;
		pos.x = 0;
		pos.y = 0;
		//*/

		gdn::Sprite back;

		back.SetImage( *imageBackground );
		back.SetSubRectangle( rectBackTopLeft );
		back.SetPosition( pos.x, pos.y );
		renderImage.Draw( back );

		back.SetSubRectangle( rectBackTopRight );
		back.SetPosition( pos.x + size.x - rectUpIdle.width - rectBackTopRight.width, pos.y );
		renderImage.Draw( back );

		back.SetSubRectangle( rectBackBottomLeft );
		back.SetPosition( pos.x, pos.y + size.y - rectBackTopRight.height );
		renderImage.Draw( back );

		back.SetSubRectangle( rectBackBottomRight );
		back.SetPosition( pos.x + size.x - rectUpIdle.width - rectBackTopRight.width, pos.y + size.y - rectBackTopRight.height );
		renderImage.Draw( back );

		back.SetSubRectangle( rectBackTop );
		for ( float i = pos.x + rectBackTopLeft.width; i < pos.x + size.x - rectUpIdle.width - rectBackTopRight.width; i += rectBackTop.width )
		{
			back.SetPosition( i, pos.y );
			renderImage.Draw( back );
		}

		back.SetSubRectangle( rectBackBottom );
		for ( float i = pos.x + rectBackTopLeft.width; i < pos.x + size.x - rectUpIdle.width - rectBackTopRight.width; i += rectBackBottom.width )
		{
			back.SetPosition( i, pos.y + size.y - rectBackTopRight.height );
			renderImage.Draw( back );
		}

		back.SetSubRectangle( rectBackLeft );
		for ( float i = pos.y + rectBackTopLeft.height; i < pos.y + size.y - rectBackTopRight.height; i += rectBackLeft.height )
		{
			back.SetPosition( pos.x, i );
			renderImage.Draw( back );
		}

		back.SetSubRectangle( rectBackRight );
		for ( float i = pos.y + rectBackTopLeft.height; i < pos.y + size.y - rectBackTopRight.height; i += rectBackRight.height )
		{
			back.SetPosition( pos.x + size.x - rectUpIdle.width - rectBackTopRight.width, i );
			renderImage.Draw( back );
		}

		back.SetSubRectangle( rectBack );
		for ( float ix = pos.x + rectBackTopLeft.width; ix < pos.x + size.x - rectUpIdle.width - rectBackBottomRight.width; ix += rectBack.width )
		{
			for ( float iy = pos.y + rectBackTopLeft.height; iy < pos.x + size.y - rectBackBottomRight.height; iy += rectBack.height )
			{
				back.SetPosition( ix, iy );
				renderImage.Draw( back );
			}
		}

		//pos = posTmp;
	}

	void Scroller::DrawBar( gdn::RenderImage& renderImage )
	{
		gdn::Sprite barTop;
		barTop.SetImage( *imageScrollBar );
		barTop.SetSubRectangle( *barTopRect );
		barTop.SetPosition( bar.x, bar.y );
		renderImage.Draw( barTop );

		gdn::Sprite barMiddle;
		barMiddle.SetImage( *imageUpSet );
		barMiddle.SetSubRectangle( *barRect );

		for ( float i = bar.y + rectBarTopIdle.height; i < bar.y + bar.height - rectBarBottomIdle.height; i += rectBarIdle.height )
		{
			//std::cout << bar.x << ", " << i << std::endl;
			barMiddle.SetPosition( bar.x, i );
			renderImage.Draw( barMiddle );
		}

		gdn::Sprite barBottom;
		barBottom.SetImage( *imageScrollBar );
		barBottom.SetSubRectangle( *barBottomRect );
		barBottom.SetPosition( bar.x, bar.y + bar.height - rectBarBottomIdle.height );
		renderImage.Draw( barBottom );
	}

	void Scroller::ResizeBar()
	{
		float barPercent = 8.f / list.size();
		if ( barPercent > 1.0 )
		{
			barPercent = 1.0;
		}

		bar.height = max( ( size.y - ( rectUpIdle.height + rectDownIdle.height ) ) * barPercent, 1 );
		/*
		std::cout << bar.height << std::endl;
		std::cout << "( size.y - ( rectUpIdle.height + rectDownIdle.height ) ) * barPercent" << std::endl;
		std::cout << "( " << size.y << " - ( " << rectUpIdle.height << " + " << rectDownIdle.height << " ) ) * " << barPercent << std::endl;
		std::cout << std::endl;
		*/
	}

	void Scroller::MoveBar()
	{
		if ( hold == true )
		{
			gdn::Vector2f newBarPos( bar.x, mousePos.y );

			if ( mousePos.y < pos.y + rectUpIdle.height + ( bar.height / 2 ) )
			{
				newBarPos.y = pos.y + rectUpIdle.height + ( bar.height / 2 );
			}
			else if ( mousePos.y > pos.y + size.y - rectDownIdle.height - ( bar.height / 2 ) )
			{
				newBarPos.y = pos.y + size.y - rectDownIdle.height - ( bar.height / 2 );
			}

			int barPos = newBarPos.y - ( bar.height / 2 );
			bar.y = barPos;
		}
	}

	void Scroller::CalculateDisplayText()
	{
		if ( list.size() < 8 )
		{
			top = 0;
			bottom = list.size();
			bar.y = pos.y + rectUpIdle.height;
			return;
		}

		float increment = ( size.y - ( rectUpIdle.height + rectDownIdle.height ) ) / list.size();

		float itemPos = ( bar.y - ( pos.y + rectUpIdle.height ) ) / increment;
		float barHeight = bar.height / increment;
		//float totalHeight = ( size.y - ( rectUpIdle.height + rectDownIdle.height ) ) / increment;

		top = itemPos;
		bottom = min( ( itemPos + barHeight ), list.size() );

		if ( top < 0 or top > bottom )
		{
			top = 0;
			bottom = min( 8, list.size() );
			bar.y = pos.y + rectUpIdle.height;
		}

		if ( list.size() > 8 and bottom - top < 8 )
		{
			top = list.size() - 8;
			bottom = list.size();
			bar.y = pos.y + size.y - rectDownIdle.height - bar.height;
		}

		//std::cout << top << " and " << bottom << " vs " << list.size() << std::endl;
	}

	void Scroller::ProcessTextSelection( const gdn::Event& event )
	{
		if ( event.type != gdn::Event::MouseButtonPressed or event.mouseButton != gdn::Mouse::Left )
		{
			return;
		}

		if ( mousePos.y < pos.y or mousePos.y > pos.y + size.y or
		     mousePos.x < pos.x or mousePos.x > size.x - rectUpIdle.width )
		{
			return;
		}

		gdn::Vector2f mousePos2 = mousePos;

		if ( mousePos2.y < pos.y )
		{
			current = list.size();
			return;
		}

		mousePos2.y -= pos.y;
		//mousePos2.y -= 10;

		current = top + ( mousePos2.y / imageHighlight->GetSize().y );
	}
}
